Smart programmers have always found ways to reuse data structures and functions. Reusing them is a good idea-they are already tested, they behave in understood ways, and they often represent data or actions that many programs need.
However, once you move a data structure from one program to another, you discover that you need some functions to manage it. Or, when you move a function, you discover the need for a data structure for it to manipulate. While traditional programming languages don't provide much support for this sort of reuse, good programmers have still done it.
You can change the contents of the object's data structure using the functions that come with the object. These functions don't need a separate data structure. You can think of the object as a component.
Combining the two basic entities-data structures and functions-into objects makes it easier to move them to other programs and reuse them. Data structures and functions that represent the information and tasks of a particular enterprise can be saved and shared, making programming tasks quicker to complete and easier to maintain.
An object controls access to its data by making data inaccessible from outside-you can only get and set its values by using functions for getting and setting that are provided by the object. The correct object-oriented programming term is encapsulation. Encapsulation allows an object to provide quality control for its own data.
In object-oriented programming, such a template is called a class. Every object is built from instructions that are associated with its class. Objects of the same type are instances of the same class. Every object is an instance of any classes that it belongs to or inherits from.
Dog
. Properties that are associated with a particular dog are called instance variables. You might also want to define properties for your program that are associated with all dogs-for example, you might want to keep track of the population of dogs. Properties that are associated with the Dog
class in general are called class variables.Now that you have a data structure for a dog, the next step in designing a program is to figure out what a dog does. A dog performs certain activities, which most programmers would refer to as procedures or functions. In your dog program, these functions might include eat, sleep, run, fetch, bark, bite, and sniff. In object-oriented programming, the actions that an object carries out are sometimes called behaviors.
If you wrote a procedural program, most of your program would consist of functions. In ScriptX, functions that are associated with a particular object or class of objects are called methods, in part to differentiate them from functions that are not associated with any object. To summarize, functions that are associated with objects are called methods; those that are not associated with objects are called simply functions.
--
-- this code example, and others throughout this volume, are on
-- the CD-ROM with the ScriptX Language and Class Library
--
class Dog()
instance variables
name, owner, breed, age, length, weight, sex, temperament
instance methods
method bark self -> print "makes a lot of noise"
method fetch self -> print "fetches a stick"
method sniff self -> print "sticks nose into things"
end
Dog
The return line ( Dog
) indicates that the ScriptX bytecode compiler has compiled the Dog
class. That means you can now create Dog
objects.
The next four lines of code create the first Dog
object, which is assigned to the variable nikki
. In this example, the value of four of the instance variables for nikki
are set at initialization. Since the other four are ignored, their values will be undefined
until they are explicitly set.
object nikki (Dog)
settings name:"Nikki", owner:#("Jocelyn","Ken"), sex:@female,
breed:"English Springer Spaniel", temperament:@nervous
end
Dog@0xef8908
Instance variables are like slots or buckets. You can put any ScriptX object into the "slots" that are defined for the dog. This example uses several of the most common classes of object in the system. The dog's name
instance variable contains a StringConstant
object "Nikki"
. The dog's owner
instance variable contains an Array
object. Each item in that array is also a StringConstant
object. The sex
and temperament
instance variables contain NameClass
objects, also known as name literals.
The return value ( Dog@0xef8908
) tells you the Dog
object exists, and it gives the memory address. Of course, this memory address will differ with each computing session, and on each platform. (Unlike the real Nikki, this dog stays put at 0xef8908
until you no longer want her around!)
Ignoring for now the ScriptX language syntax, notice how easy it is get back information about nikki
. Here's what you type into the Listener to get access to the owner
instance variables defined by the object nikki
.
nikki.owner
#("Jocelyn", "Ken")
Now consider the three instance methods defined for the class Dog
: bark
, fetch
, and sniff
. In object-oriented programming you can think of calling a method as sending a message to an object, telling that object to perform some operation.
fetch nikki
"fetches a stick"
OK
The third line ( OK
) is the return value of the fetch
method. The print
function in the definition of the fetch
method prints "fetches a stick"
to the Listener window. This string is not itself a return value-printing a string is just an operation that fetch
carries out. Throughout this manual, an arrow ( ) indicates output from ScriptX to your Listener or debugger window, including both printed messages and return values. Output from ScriptX is also indented, to indicate that you do not type it in.
This example shows how you can create a description of a dog's properties and behavior as part of a dog template. This template is the Dog
class, from which usable Dog
objects can be created. Each Dog
from this template has the same properties and the same general behavior, as described by its methods.
Note that each dog could behave differently, even though all Dog
objects share the same behavior. This is because a dog's methods have access to the object's own data. You could write a fetch
method such that a Dog
object with good temperament fetches faster than a Dog
object with bad temperament.
Object-oriented programming is characterized by inheritance. This means that a specialized class inherits the properties and behavior of its parent, which leads to a hierarchy of classes, grouped together based on similarities in their data structures and methods. This grouping of classes provides a family tree of classes that range from general to specialized.
Going back to the dog example, say you want a world that is populated by many kinds of dogs. The example created one class, Dog
. For some programs, that might be adequate. But what if you want to create a program that really models all the different ways that dogs behave?
Object-oriented programmers often design a hierarchy of classes that reflect the specialization of behavior and properties they need to model. The Dog
class has general properties and behavior for all dogs. Using inheritance and specialization, you can specialize that Dog
class to create more specialized classes that have particular characteristics and behavior. For example, you could divide dogs into various categories like hunting dogs and lap dogs as shown in Figure 1-2. There is no right way to categorize dogs; how you set up the dog hierarchy should depend on how you want to use them. Think of all the ways to divide up the world of dogs!
Technically, when your ScriptX program calls a method, it does not call the method directly. For each method name, ScriptX creates a generic function. Unlike regular functions, generic functions are always associated with an object. Generic functions look just like regular functions, except that the first argument is always an object. You always call a generic function on a particular object. ScriptX determines which method to call, based on the value of the first argument to the function, which is the object you are calling the function on. Methods are really versions of generic functions, defined for a particular class or object.
Generic functions reduce the complexity of the system of objects. A programmer can get work done using the small vocabulary of generic functions that is shared by a family of objects. Rather than a single function with a single behavior, a generic function stands for a whole range of general behavior, shared by a whole set of objects. Each class of object supplies an appropriate implementation of the generic function . A programmer can easily learn to use all dog classes, since every dog implements bark
, fetch
, and sniff
-the advantage is that each dog can implement them in its own way. For example, each specification of the Dog
class can have its own implementation of bark
. The following example shows how to specialize the Dog
class by creating two new subclasses: HuntingHound
and LapDog
. In object-oriented programming terminology, Dog
is a superclass of HuntingHound
and LapDog
. HuntingHound
and LapDog
are subclasses of Dog
. Notice the specialization of the bark
generic function on the HuntingHound
and LapDog
classes.
class HuntingHound (Dog)
instance methods
method bark self -> print "wooof, wooof"
end
HuntingHound
class LapDog (Dog)
instance methods
method bark self -> print "yip, yip, yip, yip, yip"
end
LapDog
Hunting hounds and lap dogs continue to share the same implementation of the fetch
and sniff
methods, since they are not specialized. The bark
method, although it was already defined by the Dog
class, has been redefined. In object-oriented programming terminology, to specialize behavior in this manner is to override a method.
To override a method is not necessarily to replace it with a completely new version. A subclass can invoke the version of a method that is provided by a superclass in its own implementation of that method. Here is an example.
class Shihtzu (LapDog)
instance methods
method bark self -> (
nextMethod self
print "jumps up and down"
)
end
Shihtzu
object tyler (Shihtzu) settings name:"Tyler" end
Shihtzu@0xefcf88
bark tyler
"yip, yip, yip, yip, yip"
"jumps up and down"
OK
In the example above, the expression nextMethod self
causes any instances of Shihtzu
to call the bark
method defined by the next superclass that implements it. In this case, the next implementing superclass is LapDog
. Each instance of Shihtzu
first calls the more general bark
method for all instances of LapDog
, and then continues with the specialized version for a Shihtzu
object.
Polymorphism, the sharing of behavior, allows you to be more general because you are specific in indicating which object should fulfill the request. The programmer uses the generic function to describe what should be done and leaves it to the object to determine how exactly to do it.
By comparison, functions in a procedural program have only a single version. You must create a separate function or add special case code and additional arguments for each new thing you want to do. Modifying a procedural program increases the complexity of the system, and makes it harder for you to maintain and scale your code.
Dog
class defines a protocol in the bark
, fetch
, and sniff
methods. These three methods are implemented for every instance of Dog
, making them the Dog
protocol. A protocol is a set of generic functions that is defined for every class in some branch of a class hierarchy.In some object-oriented programming environments, the concept of protocol is much more formal. In ScriptX, it is an informal term that identifies a set of shared behaviors. As a programmer, it makes your life easy to know that every dog, whether a golden retriever or a cocker spaniel, knows how to bark. Each subcategory of dog, each individual dog breed, or even each individual dog, can define its own bark, but you are guaranteed that if you have a dog, it knows how to bark.
BassetHound
class that implements the drool
method, a method that other dogs, with less active salivary glands and more active facial muscles, might not share. In the dog world example, drool behavior is peculiar to basset hounds.class BassetHound (HuntingHound)
instance methods
method drool self -> print "slobbers all over everything"
end
-- now create an instance of BassetHound
object vaps (BassetHound)
settings name:"Vaps", owner:"The Crosbys"
end
-- call the drool generic function on vaps
drool vaps
Note the following distinction, which is important in object-oriented programming. In the previous example, it isn't the"slobbers all over everything"
BassetHound
class that drools. The BassetHound
class is a template used to define the properties and behavior of basset hounds. To be more precise, the BassetHound
class is a template that defines the ways in which basset hounds differ from hunting dogs, and from dogs in general. But you have to create an actual instance of BassetHound
, a BassetHound
object, in order to see a dog drool.Dog
. That might do if you were only interested in one aspect of a dog's behavior. But in the real world, systems are far more complex. Some unlucky dogs are wild or stray, and do not have owners. Instead, they have territories and live in packs. Other dogs are tame, and have both owners and veterinarians. Naturally, wild dogs behave very differently from pets. But so far, the dog world example is set up as if all dogs have a property called owner
, an instance variable that stores the owner's name.
Fortunately, ScriptX allows any object or class to inherit from more than one parent class. This multiple inheritance allows you to factor the behavior of dogs into several different parent classes. To factor behavior is to divide or separate aspects of behavior in logical ways. The ability to factor information or behavior is one of the benefits of multiple inheritance. For example, you can keep the Dog
class, renamed Canine
, and create two new classes, Pet
and WildAnimal
, that contain aspects of canine life that are peculiar to domestic and feral dogs (see Figure 1-3). Mixing these classes together will produce dogs with the desired characteristics.
PetDog
class that uses multiple inheritance to define properties and behavior. The new class is a template for creating objects that share characteristics of all of its parents. PetDog
inherits from both the Canine
and Pet
classes, which incorporate the general properties and behavior of the previously defined Dog
class, and add new ones. Dog
class until you eliminate all instances of Dog
from the system.
Instead of reusing Dog
, this example uses the name Canine
as a root class for
dogs.
class Canine ()
instance variables
age, length, weight, sex, temperament
instance methods
method bark self -> print "makes a lot of noise"
method sniff self -> print "sticks nose into things"
method sleep self -> print "lazy dog sleeps all day"
end
class Pet ()
instance variables
name, owner, breed, veterinarian, spayed
instance methods
method fetch self -> print "fetches a stick"
end
class PetDog (Pet, Canine)
end
The PetDog
class does not actually define any new behavior or properties for PetDog
objects. An instance of PetDog
will have all the instance variables and instance methods that its superclasses define. Now create a PetDog
object.
object tammy (PetDog)
settings name:"Tammy", owner:"the Metzenbergs", sex:@female,
spayed:@true, breed:"Siberian Husky", veterinarian:"Dr. Donovan"
end
fetch tammy
"fetches a stick"
sniff tammy
"sticks nose into things"
PetDog
object tammy
defines properties of both a Canine
object and a Pet
object. She barks, sniffs, and sleeps like a canine, and she fetches like a pet.
Although you would probably want to override the fetch
method, you could easily create a Cat
class and combine it with Pet
to create the new class PetCat
. In object-oriented terminology, Pet
is being used to mix in characteristics of Pet
with another class to create a new subclass that offers additional features.
As you work with ScriptX, you will find many examples of multiple inheritance in use. With multiple inheritance, it is easy to add new features to classes and objects you create. ScriptX ships with a library of media classes called the core classes. You can add functionality to your own classes by mixing them in with these predefined media classes. For example, if you wanted to have your PetDog
object appear in a ScriptX window, you could create a new class that mixes PetDog
with TwoDShape
, one of the core classes. As an instance of TwoDPresenter
, a TwoDShape
object can present a graphic object, such as a bitmap. Mixing in the TwoDShape
class would give your objects the ability to display a target object (such as a bitmap) in a window with a two-dimensional coordinate system.
In an object-oriented environment, you can isolate changes so that they have an effect on only a particular class, a group of classes, or even a particular object. Suppose you have a pet dog that sleeps on top of his doghouse. You don't have to create a new class, or modify any existing classes. You can add this new behavior to a particular PetDog
object when you create the object.
object snoopy (PetDog)
instance methods
method sleep self -> (
print "sleeps on top of his doghouse"
nextMethod self
)
settings name:"Snoopy", owner:"Charlie Brown", breed:"Beagle"
end
sleep snoopy
As this example shows, specialization in ScriptX is not limited to defining new classes. You can add new instance variables and define or override instance methods at any level, even for a single object."sleeps on top of his doghouse"
"lazy dog sleeps all day"
OK
Suppose you want to know where pet dogs live. As defined in the previous section, the PetDog
class inherits from both Pet
and Canine
. From the Pet
class, a pet dog inherits the instance variable owner
. If an owner has an address
instance variable, you can get access to that information through the dog. The following example defines the Owner
class and creates an instance of Owner
and an instance of PetDog
.
class Owner ()
instance variables
name, address
end
-- create an owner
object janne (Owner)
settings name:"Janne", address:"Fairfax, California, USA"
end
-- create a dog for the owner to own
object odan (PetDog)
settings breed:"Mastiff", sex:@male, name:"Odan"
end
If the dog's owner
instance variable is set, you can figure out where the dog lives by examining the owner's address
instance variable.
odan.owner := janne
odan.owner.address
"Fairfax, California, USA"
This connection makes additional information available about a pet dog, without modifying the Canine
or PetDog
classes. (In an actual program, you could make this connection at initialization, and introduce error and type checking.)
Object-oriented libraries are collections of classes arranged in hierarchies that resemble family trees. The library classes and the objects created from those classes are complete and ready to use. They can address larger scale problems than function libraries can. The programmer can use them to accelerate development, making it possible to prototype rapidly by using library objects as a base for exploration.
More importantly, it is possible for an object-oriented system to provide a set of objects that have built-in relationships. By creating collections of cooperating objects, it's possible for an object-oriented system's libraries to act as pretested problem-solving frameworks. The framework provides the majority of the code. You plug your objects into the slots left in the framework.
It's easy to modify object-oriented systems without breaking them, because the changes are localized in individual objects, not spread out across many data structures and functions. Adding new behavior to an object-oriented system can often be as simple as creating a new type of object and placing an object of that type in an existing system of code. The system is modified by changing the type of the object that receives a method. The trusted, tested sequence of logic doesn't have to change.
Another special kind of class, common throughout the core classes, is an abstract class. When a class hierarchy is organized, information is factored into superclasses so that common subclasses can share and reuse it. Often, when information is factored in this way, classes can result that provide partial information for their subclasses, but do not contain enough information to create fully-featured instances of themselves. These classes are called abstract classes. An abstract class can be subclassed or mixed in with another class, but it cannot be instantiated. Abstract classes exist as a basis for defining other classes. ScriptX prevents you from instantiating an abstract class. Any class that is not abstract is concrete.
A final distinction is between scripted classes and the core classes themselves. A scripted class is one that is created using the ScriptX language. (The core classes are created with Objects-In-C, an extension of the C programming language developed by Kaleida Labs, Inc.) All scripted classes are unsealed. Although there are some internal differences between scripted classes and the core classes, their external interface is the same.
Classes are organized in a hierarchy that resembles a family tree, based on the characteristics they share. Classes inherit the properties and behavior of their parent classes. When it is created, or instantiated, each object gets a copy of the instance variables and access to the instance methods that are defined for its class.
With each new class of objects, a programmer is free to define new instance variables and methods, or to override existing methods to behave in a new, appropriate way for the particular class. This redefinition can even incorporate the parent's version of the same method.
This class inheritance tree ranges from very general types at its root to more specialized types at the branches. The types in a particular branch of the tree share a common way of doing things. New classes can be added at any point in the tree. Almost any existing type can be the parent for a new type.
Much of the real power of an object-oriented environment is found in the class libraries that can be included with an object-oriented language. They allow a programmer to concentrate on the problem at hand, building solutions from reliable, tested components.
The primary benefits of object-oriented programming are reduced complexity, increased reusability, and extensibility. Objects are a way of bundling data and functions together to make a reusable software component. Objects protect their internal workings, hiding their data and generalizing their functions so that there are fewer details for a programmer to deal with. Classes are factories for making usable objects. Classes provide the basis for making new types simply by describing how the new type is different from an existing type.
Object-oriented programming gives you the ability to create a general framework which, because of polymorphism, continues to work with new and unexpected specializations of the system. All the basic frameworks in ScriptX, such as the animation compositor, depend on polymorphism. Because of polymorphism, you can rely on existing frameworks to work with new objects that you define in your title.
This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.